/*
 * Copyright(c) 2002, 2003, 2004, 2005, 2007 by Christian Nowak <chnowak@web.de>
 * Last update: 20th October, 2007
 */

#include <stdlib.h>
#include <string.h>

#include "modplay.h"
#include "s3m.h"
#include "effects.h"


#define DESCRIPTION "Future Crew Screamtracker 3"
#define AUTHOR			"Christian Nowak <chnowak@web.de>"
#define VERSION		 "v0.01b"
#define COPYRIGHT	 "Copyright(c) 2003, 2007"


#define SAFE_MALLOC(dest, a) \
	if(!(dest = myMalloc(a))) { \
		MODFILE_Free(s3m); \
		return -2; \
	}


/**
 * int MODFILE_SetS3M(u8 *modfile, int modlength, MODFILE *mod);
 *
 * Processes the raw data of an S3M file and copies it to a
 * structure. The structure can then be used as a handle
 * of the S3M file. The original raw data isn't needed by the
 * handle.
 *
 * Returns a value <0 on error.
 *
 * Parameters:
 * modfile	 - A pointer to the raw S3M data
 * modlength - The length of the raw data in bytes
 * mod			 - A pointer to the S3M handle
 **/
int MODFILE_SetS3M(u8 *s3mfile, int s3mlength, MODFILE *s3m) {

	int ofs = 0;
	int i;
	BOOL panning_present = FALSE;
	int remapChannels[MODPLAY_MAX_CHANNELS];

	if(!MODFILE_IsS3M(s3mfile, s3mlength) ||(s3m == NULL))
		return -1;

	/* 0 */
	memcpy(s3m->songname, &s3mfile[ofs], 28);
	ofs+= 28;
	/* 1c */
	ofs++;
	/* 1d */
	if(s3mfile[ofs++] != 0x10) {

		return -1;
	}

	ofs += 2;

	/* 20 */
	s3m->songlength	= s3mfile[ofs++];
	s3m->songlength |= s3mfile[ofs++] << 8;

	/* 22 */
	s3m->nInstruments	= s3mfile[ofs++];
	s3m->nInstruments |= s3mfile[ofs++] << 8;
	s3m->nSamples = s3m->nInstruments; /* S3M doesn't have multisamples */

	/* 24 */
	s3m->nPatterns	= s3mfile[ofs++];
	s3m->nPatterns |= s3mfile[ofs++] << 8;

	/* 26 */
	s3m->st2_vibrato			=(s3mfile[ofs] & 0x02) != 0;
	s3m->st2_tempo				=(s3mfile[ofs] & 0x04) != 0;
	s3m->amiga_sliding		=(s3mfile[ofs] & 0x08) != 0;
	s3m->optimize_vols		=(s3mfile[ofs] & 0x10) != 0;
	s3m->amiga_boundaries =(s3mfile[ofs] & 0x20) != 0;
	s3m->enable_sfx			 =(s3mfile[ofs] & 0x40) != 0; /* Fast volume slides */
	ofs += 2;

	/* 28 */
	s3m->tracker_version =(s3mfile[ofs] << 8) | s3mfile[ofs + 1];
	ofs += 2;

	/* 2a */
	s3m->unsigned_samples =(s3mfile[ofs] == 2);
	ofs += 2;

	/* 2c */
	if(memcmp(&s3mfile[ofs], "SCRM", 4) != 0) {

		return -1;
	}
	ofs += 4;

	/* 30 */
	s3m->master_volume = s3mfile[ofs++];
	/* 31 */
	s3m->start_speed = s3mfile[ofs++];
	/* 32 */
	s3m->start_tempo = s3mfile[ofs++];
	/* 33 */
	ofs++;
	/* 34 */
	ofs++;
	/* 35 */
	panning_present =(s3mfile[ofs++] == 252);
	/* 36 */
	ofs = 0x40;

	/* 40 */
	for(i = 0; i < MODPLAY_MAX_CHANNELS; i++) {

		s3m->channels[i].voiceInfo.enabled = FALSE;
		remapChannels[i] = 255;
	}

	for(i = s3m->nChannels = 0; i < 32; i++, ofs++) {

		if(s3mfile[ofs] < 16) {

			remapChannels[i] = s3m->nChannels;
			s3m->nChannels++;
			s3m->channels[remapChannels[i]].voiceInfo.enabled = TRUE;
			s3m->channels[remapChannels[i]].default_panning = s3mfile[ofs] < 8 ? 86 : 167;
		}
	}
	s3m->nChannels++; /* Global FX channel */

	/* 60 */
	memcpy(s3m->playlist, &s3mfile[ofs], s3m->songlength);
	ofs += s3m->songlength;

	/* Parapointers to intruments */
	SAFE_MALLOC(s3m->instruments, s3m->nInstruments * sizeof(MOD_Instrument));
	memset(s3m->instruments, 0, s3m->nInstruments * sizeof(MOD_Instrument));
	SAFE_MALLOC(s3m->samples, s3m->nSamples * sizeof(MOD_Sample));
	memset(s3m->samples, 0, s3m->nSamples * sizeof(MOD_Sample));

	for(i = 0; i < s3m->nInstruments; i++, ofs += 2) {

		int instrPointer =(s3mfile[ofs] | s3mfile[ofs+1] << 8) << 4;

		s3m->instruments[i].volumeFade = 32767;
		/* 0 */
		/*		s3m->instruments[i].adlib = s3mfile[instrPointer++] != 1;*/
		if(/*!s3m->instruments[i].adlib*/s3mfile[instrPointer++] == 1) {

			int sampleseg;
			int j;
			int temp;

			s3m->samples[i].relative_note = 0;
			s3m->samples[i].panning = 255;

			/* 1 */
			/*			memcpy(s3m->instruments[i].dosname, &s3mfile[instrPointer], 12);
				s3m->instruments[i].dosname[12] = '\0';*/
			instrPointer += 12;

			/* d */
			sampleseg = s3mfile[instrPointer + 1] |
	(s3mfile[instrPointer + 2] << 8) |
	(s3mfile[instrPointer + 0] << 16);
			sampleseg <<= 4;
			instrPointer += 3;

			/* 10 */
			s3m->samples[i].sampleInfo.length = s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 2] << 24);
			instrPointer += 4;

			/* 14 */
			s3m->samples[i].sampleInfo.loop_start = s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 3] << 24);
			instrPointer += 4;

			/* 18 */
			s3m->samples[i].sampleInfo.loop_end = s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 3] << 24);
			instrPointer += 4;

			/* 1c */
			s3m->samples[i].default_volume = s3mfile[instrPointer++];

			/* 1d */
			instrPointer++;

			/* 1e */
			/*			s3m->instruments[i].packed =(s3mfile[instrPointer++] == 1);*/
			instrPointer++;

			/* 1f */
			s3m->samples[i].sampleInfo.looped =(s3mfile[instrPointer] & 1) != 0;
			s3m->samples[i].sampleInfo.stereo =(s3mfile[instrPointer] & 2) != 0;
			s3m->samples[i].sampleInfo.bit_16 =(s3mfile[instrPointer] & 4) != 0;
			s3m->samples[i].sampleInfo.pingpong = FALSE;
			instrPointer++;

			if(!s3m->samples[i].sampleInfo.looped) {

	s3m->samples[i].sampleInfo.loop_start =
		s3m->samples[i].sampleInfo.loop_end =
		s3m->samples[i].sampleInfo.length - 1;
			}

			/* 20 */
			s3m->samples[i].default_middle_c = s3m->samples[i].middle_c =
	s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 3] << 24);
			instrPointer += 4;

			/* 24 */
			instrPointer += 4;

			/* 28 */
			instrPointer += 2;

			/* 2a */
			instrPointer += 2;

			/* 2c */
			instrPointer += 4;

			/* 30 */
			memcpy(s3m->instruments[i].name, &s3mfile[instrPointer], 28);
			instrPointer += 28;

			/* 4c */
			if(memcmp(&s3mfile[instrPointer], "SCRS", 4) != 0) {

	return -1;
			}

			/* Define a new instrument */
			strcpy(s3m->instruments[i].name, s3m->samples[i].name);
			for(j = 0; j < 256; j++) {

	s3m->instruments[i].samples[j] = &s3m->samples[i];
	s3m->instruments[i].note[j] = j;
			}

			/* Disable instrument envelopes */
			s3m->instruments[i].envVolume.enabled = FALSE;
			s3m->instruments[i].envPanning.enabled = FALSE;

			/* Copy sample data */
			temp = s3m->samples[i].sampleInfo.length *
	(s3m->samples[i].sampleInfo.bit_16 || s3m->samples[i].sampleInfo.stereo ? 2 : 1);

			SAFE_MALLOC(s3m->samples[i].sampleInfo.sampledata, temp);
			memset(s3m->samples[i].sampleInfo.sampledata, 0, temp);

			if(s3m->samples[i].sampleInfo.bit_16) {

	u8 *di = &s3mfile[sampleseg];
	int l = s3m->samples[i].sampleInfo.length;

	for(j = 0; j < l; j++) {

		u16 d;

		d = di[j<<1] | di[(j << 1) + 1] << 8;
		if(s3m->unsigned_samples)
			d ^= 0x8000;
		((u16*)s3m->samples[i].sampleInfo.sampledata)[j] = d;
	}
			} else {

	u8 *di = &s3mfile[sampleseg];
	int l = s3m->samples[i].sampleInfo.length;

	for(j = 0;j < l; j++) {

		u8 d;

		d = di[j];
		if(s3m->unsigned_samples)
			d ^= 0x80;
		((u8*)s3m->samples[i].sampleInfo.sampledata)[j] = d;
	}
			}
		}
	}

	/* Parapointers to patterns */
	SAFE_MALLOC(s3m->patterns, s3m->nPatterns * sizeof(MOD_Note*));
	memset(s3m->patterns, 0, s3m->nPatterns * sizeof(MOD_Note*));
	SAFE_MALLOC(s3m->patternLengths, s3m->nPatterns * sizeof(int));

	for(i = 0; i < s3m->nPatterns; i++, ofs += 2) {

		int pattPointer =(s3mfile[ofs] | s3mfile[ofs + 1] << 8) << 4;
		int pattPackedLen;
		int destChannel;
		int destLine;

		s3m->patternLengths[i] = 64;
		SAFE_MALLOC(s3m->patterns[i], sizeof(MOD_Note) * s3m->nChannels * 64);
		memset(s3m->patterns[i], 255, sizeof(MOD_Note) * s3m->nChannels * 64);

		pattPackedLen = s3mfile[pattPointer] |
			(s3mfile[pattPointer + 1] << 8);
		pattPointer += 2;

		destChannel = destLine = 0;

		while(1) {

			int dest;
			u8 compByte = s3mfile[pattPointer++];

			if(compByte == 0) {

				destLine++;
				if(destLine >= 64) {

					break;
				}
			} else {

				destChannel = remapChannels[compByte & 31];
				dest =(destLine * s3m->nChannels) + destChannel ;
				if(destChannel != 255) {

					/* Note/Instrument */
					if(compByte & 32) {

						s3m->patterns[i][dest].note = s3mfile[pattPointer++];
						s3m->patterns[i][dest].instrument = s3mfile[pattPointer++];
					}

					/* Volume byte */
					if(compByte & 64) {

						s3m->patterns[i][dest].volume = s3mfile[pattPointer++];
					}

					/* Effect number/parameter */
					if(compByte & 128) {

						u16 effect = s3mfile[pattPointer++];
						u8 operand = s3mfile[pattPointer++];

						/* Convert effect/operand to internal format */
						switch(effect) {
							
							case 'A' - 'A' + 1:	/* Set Speed */
								effect = EFFECT_S3Ma0;
								break;

							case 'B' - 'A' + 1: /* Jump To Order xy */
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STb0;
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
								effect = EFFECT_NONE;
								operand = 0;
								break;
								
							case 'C' - 'A' + 1: /* Break Pattern */
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STd0;
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
								effect = EFFECT_NONE;
								operand = 0;
								break;
								
							case 'D' - 'A' + 1: /* Volume Slide */
								effect = EFFECT_S3Md0;
								break;

							case 'E' - 'A' + 1: /* Portamento Down */
								effect = EFFECT_S3Me0;
								break;

							case 'F' - 'A' + 1: /* Portamento Up */
								effect = EFFECT_S3Mf0;
								break;
							
							case 'G' - 'A' + 1: /* Tone Portamento */
								effect = EFFECT_ST30;
								break;
							
							case 'H' - 'A' + 1: /* Vibrato */
								effect = EFFECT_ST40;
								break;
							
							case 'I' - 'A' + 1: /* Tremor */
								effect = EFFECT_NONE;
								break;
							
							case 'J' - 'A' + 1: /* Arpeggio */
								effect = EFFECT_ST00;
								break;
							
							case 'K' - 'A' + 1: /* Vibrato + Volume Slide */
								effect = EFFECT_S3Mk0;
								break;
								
							case 'L' - 'A' + 1: /* Tone Portamento + Volume Slide */
								effect = EFFECT_S3Ml0;
								break;
								
							case 'O' - 'A' + 1: /* Sample Offset */
								effect = EFFECT_ST90;
								break;
							
							case 'Q' - 'A' + 1: /* Retrig + Volume Slide */
								effect = EFFECT_S3Mq0;
								break;
							
							case 'R' - 'A' + 1: /* Tremolo */
								effect = EFFECT_S3Mr0;
								break;
							
							case 'S' - 'A' + 1: /* Various */
							
								switch((operand >> 4) & 0x0f) {
									
									case 0x02: /* Set Finetune */
										effect = EFFECT_STe5;
										operand = operand & 0x0f;
										break;
									
									case 0x03: /* Vibrato Waveform */
										effect = EFFECT_STe4;
										operand = operand & 0x0f;
										break;
									
									case 0x04: /* Tremolo Waveform */
										effect = EFFECT_STe7;
										operand = operand & 0x0f;
										break;
									
									case 0x08: /* Panning */
										effect = EFFECT_STe8;
										operand = operand & 0x0f;
										break;
									
									case 0x0b: /* Pattern Loop */
										operand = operand & 0x0f;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STe6;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
										effect = EFFECT_NONE;
										operand = 0;
										break;
									
									case 0x0c: /* Note Cut */
										effect = EFFECT_STec;
										operand = operand & 0x0f;
										break;
									
									case 0x0d: /* Note Delay */
										effect = EFFECT_STed;
										operand = operand & 0x0f;
										break;

									case 0x0e: /* Pattern Delay */
										operand = operand & 0x0f;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STee;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
										effect = EFFECT_NONE;
										operand = 0;
										break;

									default:
										effect = EFFECT_NONE;
										break;
								}
								break;
								
							case 'T' - 'A' + 1: /* Set Tempo */
								effect = EFFECT_S3Mt0;
								break;
							
							case 'V' - 'A' + 1: /* Set Global Volume */
								effect = EFFECT_XM10;
								break;

							default:
								effect = EFFECT_NONE;
								break;
						}
						
						s3m->patterns[i][dest].effect[0] = effect;
						s3m->patterns[i][dest].operand[0] = operand;
					}
				} else {

					if(compByte & 32)
						pattPointer += 2;
					if(compByte & 64)
						pattPointer++;
					if(compByte & 128)
						pattPointer += 2;
				}
			}
		}
	}

	s3m->filetype = MODULE_S3M;
	return 0;
}




/**
 * BOOL MODFILE_IsS3M(u8 *modfile, int modlength);
 *
 * Checks whether the raw data in memory is a valid
 * S3M file.
 *
 * Returns TRUE if the data is an S3M file, FALSE
 * if not.
 *
 * Parameters:
 *
 * modfile	 - Pointer to the raw data to be checked
 * modlength - Length of the raw data in bytes
 **/
BOOL MODFILE_IsS3M(u8 *modfile, int modlength) {

	if((modfile == NULL) ||(modlength < 0x30))
		return FALSE;

	return memcmp(&modfile[0x2c], "SCRM", 4) == 0;
}




int MODFILE_S3MGetFormatID(void) {

	return MODULE_S3M;
}

char *MODFILE_S3MGetDescription(void) {

	return DESCRIPTION;
}

char *MODFILE_S3MGetAuthor(void) {

	return AUTHOR;
}

char *MODFILE_S3MGetVersion(void) {

	return VERSION;
}

char *MODFILE_S3MGetCopyright(void) {

	return COPYRIGHT;
}
